package lolbatfsm.system;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

/**
 * It is a finite-state machine, with its states and transitions.
 * This class also implements the methods needed to evolve its state during the simulation,
 * called by the Simulator.
 * @author Matteo Battaglio <madbat87@gmail.com>
 */
public class FSM implements Serializable {

    private final String name;
    private final Set<Transition> transitions = new TreeSet<Transition>();
    private State initialState;
    private transient State currentState;
    private transient Set<Transition> enabledTransitions;
    private transient Transition pendingTransition;
    private boolean inited = false;

    /**
     * Instantiates a new FSM with the specified name, initial State and transitions.
     * @param name the name of the finite-state machine
     * @param initialState the first state that will be set as the current state,
     * when the simulation starts
     * @param transitions the array of transitions (which includes also their source
     * and destination states)
     */
    public FSM(String name, State initialState, Transition... transitions) {
        this.name = name;
        this.initialState = initialState;
        for (int i = 0; i < transitions.length; i++) {
            //transitions[i].setOwnerFSM(this);
            if (!this.transitions.add(transitions[i])) {
                System.out.println("Transition " + transitions[i] + " non aggiunta!");
            }
        }
        init();
    }

    /**
     * Initializes the finite-state machine to prepare it for the simulation.
     */
    public void init() {
        inited = true;
        this.currentState = initialState;
        enabledTransitions = new TreeSet<Transition>();
        for (Transition transition : transitions) {
            transition.init(this);
            if (transition.getSourceState().equals(currentState)) {
                enabledTransitions.add(transition);
            }
        }
    }

    public String getName() {
        return this.name;
    }

//    public void setInitialState(State initialState) {
//        if (!inited) {
//            this.initialState = initialState;
//        } else {
//            throw new RuntimeException("Can't change initial state after init");
//        }
//    }
    
    private void setCurrentState(State currentState) {
        this.currentState = currentState;
        enabledTransitions.clear();
        for (Transition t : transitions) {
            if (t.getSourceState().equals(currentState)) {
                enabledTransitions.add(t);
            }
        }
    }

    public State getCurrentState() {
        return currentState;
    }

    public Collection<Transition> getTransitions() {
        return transitions;
    }

    /**
     * Returns the current pending transition, if it exists, null otherwise.
     * @return the current pending transition, if it exists, null otherwise.
     */
    public Transition getPendingTransition() {
        //Restituire la transition in corso, se presente
        return pendingTransition;
    }

    /**
     * Returns the transitions enabled to trigger (if any), null otherwise.
     * @return the transitions enabled to trigger (if any), null otherwise.
     */
    public Collection<Transition> getEnabledTransitions() {
        //Restituire le transition abilitate a scattare, se presenti
        return hasEnabledTransitions() ? enabledTransitions : null;
    }

    /**
     * Tells wether there is a pending transition.
     * @return true if there is a pending transition, false otherwise.
     */
    public boolean hasPendingTransition() {
        //se questo e' true, hasEnabledTransitions() deve tornare false.
        return pendingTransition != null;
    }

    /**
     * Tells wether there is at least one transition enabled to trigger.
     * @return true if there is at least one transition enabled to trigger,
     * false otherwise.
     */
    public boolean hasEnabledTransitions() {
        //se questo e' true, hasPendingTransition() deve tornare false.
        return (!hasPendingTransition() && !enabledTransitions.isEmpty());
    }

    /**
     * Executes the iteration by decreasing the specified transition's residual duration
     * by duration units. Then if the new residual duration is equal to zero it is reset
     * to the original transition's duration and the current state is set to the transition's
     * destination state, otherwise the specified transiton is set as the current pending transition.
     * @param t the transition to execute
     * @param duration the duration of the iteration, determined by the simulator
     * @throws FSMException if the specified transition is invalid.
     */
    void executeIteration(Transition t, int duration) throws FSMException {
        if (hasPendingTransition() && pendingTransition.equals(t) ||
            hasEnabledTransitions() && enabledTransitions.contains(t)) {

            enabledTransitions.remove(t);
            t.decreaseResidualDuration(duration);
            if (t.getResidualDuration() > 0) {
                pendingTransition = t;
            } else {
                t.reset();
                pendingTransition = null;
                setCurrentState(t.getDestinationState());
            }
        } else {
            throw new FSMException("Invalid transition argument");
        }
    }

    /**
     * Resets the state of the FSM to the initial state.
     */
    void reset() {
        currentState = initialState;
        enabledTransitions = null;
        pendingTransition = null;
        inited = false;
    }

    @Override
    public String toString() {
        return name;
    }

}
